﻿
Imports System.Net
Imports System.Threading
Imports std
Imports httpContext

Public Class httpServer3
    Private Shared ReadOnly HTTPSERVER3_REQUEST_10sQPS As Int64
    Private Shared ReadOnly HTTPSERVER3_REJECT_10sQPS As Int64
    Private Shared ReadOnly HTTPSERVER3_STARTCONTEXT_DELAY_TIMEMS As Int64
    Protected Shared ReadOnly HTTPSERVER3_REQUEST_RESPOND_TIMEMS As Int64
    Protected Shared ReadOnly HTTPSERVER3_REJECT_RESPOND_TIMEMS As Int64

    Public Const defaultMaxConnectionCount As Int64 = 1024
    Public Const prefixesSeparators As String = character.comma
    Public Const portsSeparators As String = character.comma + character.sheffer
    Public Event receive(ByVal context As httpContext)
    Public Event reject(ByVal context As httpContext)

    Public maxConnectionCount As Int64 = defaultMaxConnectionCount
    Private listener As HttpListener = Nothing
    Private prefixes As [set](Of String) = Nothing
    Private hddoslite As hddoslite2 = Nothing
    Private pending As Boolean = False
    Private contextpool As contextpool_t = Nothing

    <ThreadStatic()> Private Shared _currentContext As httpContext = Nothing
    Public Shared Property currentContext() As httpContext
        Get
            assert(Not _currentContext Is Nothing)
            Return _currentContext
        End Get
        Set(ByVal value As httpContext)
            assert(_currentContext Is Nothing OrElse value Is Nothing)
            _currentContext = value
        End Set
    End Property

    Private _connectionCount As Int32 = 0
    Public Function connectionCount() As Int32
        Return _connectionCount
    End Function

    Private Sub incrementConnectionCount()
        Interlocked.Increment(_connectionCount)
    End Sub

    Private Sub decrementConnectionCount()
        Interlocked.Decrement(_connectionCount)
    End Sub

    Private _stopping As Boolean = False
    Public Property stopping As Boolean
        Get
            Return _stopping
        End Get
        Private Set(ByVal value As Boolean)
            _stopping = value
        End Set
    End Property

    Public Function hddosliteEnabled() As Boolean
        Return Not hddoslite Is Nothing
    End Function

    Public Sub enableHDDoSLite(ByVal config As configParser,
                               Optional ByVal section As String = configNaming.hddos.name)
        hddoslite = New hddoslite2(config, section)
    End Sub

    Public Sub disableHDDoSLite()
        hddoslite = Nothing
    End Sub

    Public Sub addHDDoSLiteBypassPaths(ByVal ParamArray paths() As String)
        If hddosliteEnabled() Then
            hddoslite.addBypassPaths(paths)
        End If
    End Sub

    Public Sub clearprefixes()
        prefixes.clear()
        listener.Prefixes.Clear()
    End Sub

    Public Sub addprefix(ByVal prefix As String)
        prefixes.insert(prefix)
    End Sub

    Public Sub addport(ByVal port As Int32)
        addprefix(httpAddressHeader + character.asterisk + network.portMark +
                  Convert.ToString(port) + network.pathSeperator)
    End Sub

    Public Sub addprefixes(ByVal prefixes As String)
        Dim prefixset As vector(Of String) = Nothing
        strsepar(prefixes, prefixesSeparators, prefixset)
        For i As Int64 = 0 To prefixset.size() - 1
            addprefix(prefixset(i))
        Next
    End Sub

    Public Sub addports(ByVal ports As String)
        Dim portset As vector(Of String) = Nothing
        strsepar(ports, portsSeparators, portset, True)
        For i As Int64 = 0 To portset.size() - 1
            Dim port As Int32
            If convertor.toint32(portset(i), port) Then
                addport(port)
            Else
                raiseError("cannot convert port to int32, " + portset(i) + ", ignore.",
                           errorHandle.errorType.exclamation)
            End If
        Next
    End Sub

    Public Function active() As Boolean
        Return listener.IsListening()
    End Function

    Private Sub closeListener()
        listener.Stop()
    End Sub

    Public Sub [stop](Optional ByVal waitTime As Int64 = 60)
        If active() Then
            stopping = True
            closeListener()
            While connectionCount() > 0 AndAlso waitTime > 0
                sleepSeconds(1)
                waitTime -= 1
            End While
        End If
    End Sub

    Private Sub importPrefixes()
        Dim it As [set](Of String).iterator = Nothing
        listener.Prefixes.Clear()
        it = prefixes.begin()
        While it <> prefixes.end()
            Try
                listener.Prefixes.Add(+it)
            Catch
                raiseError("fail to add prefix to listener, " + (+it) + ", ignore", errorHandle.errorType.exclamation)
            End Try
            it += 1
        End While
    End Sub

    Private Function respondTimeMs(ByVal startticks As Int64) As Int64
        Return ticksToMilliseconds(Now.ticks() - startticks)
    End Function

    Protected Sub finishContext(ByVal ctx As httpContext, ByVal startticks As Int64, ByVal timeCounter As Int64)
        counter.increase(timeCounter, respondTimeMs(startticks))
        'HttpListenerContext cannot work after close,
        'while httpContext is fine.
        If hddosliteEnabled() Then
            hddoslite.releaseAccessing(ctx)
        End If
        contextpool.release(ctx)
        decrementConnectionCount()
    End Sub

    Private Sub startContext(ByVal ctx As httpContext,
                             ByVal raise As void(Of httpContext),
                             ByVal timeCounter As Int64)
        assert(Not raise Is Nothing)
        Dim startticks As Int64 = 0
        startticks = Now.ticks()
        assert_begin(New callbackAction(begin:=Function() As Boolean
                                                   currentContext() = ctx
                                                   raise(ctx)
                                                   currentContext() = Nothing
                                                   Return True
                                               End Function,
                                        check:=Function() As Boolean
                                                   Return callbackAction.true_to_pass(ctx.closed())
                                               End Function,
                                        [end]:=Function() As Boolean
                                                   finishContext(ctx, startticks, timeCounter)
                                                   Return True
                                               End Function))
    End Sub

    Protected Overridable Sub startContext(ByVal ctx As httpContext)
        startContext(ctx,
                     Sub(ByRef c As httpContext)
                         RaiseEvent receive(c)
                     End Sub,
                     HTTPSERVER3_REQUEST_RESPOND_TIMEMS)
    End Sub

    Protected Overridable Sub rejectContext(ByVal ctx As httpContext,
                                            ByVal rejectIssues As Byte)
        startContext(ctx,
                     Sub(ByRef c As httpContext)
                         c.setRejectIssues(rejectIssues)
                         RaiseEvent reject(c)
                     End Sub,
                     HTTPSERVER3_REJECT_RESPOND_TIMEMS)
    End Sub

    Private Sub startContext(ByVal ctx As HttpListenerContext, ByVal startticks As Int64)
        counter.increase(HTTPSERVER3_STARTCONTEXT_DELAY_TIMEMS, ticksToMilliseconds(Now.ticks() - startticks))

        incrementConnectionCount()
        Dim httpCtx As httpContext = Nothing
        httpCtx = contextpool.get()
        Dim pa As pointer(Of callbackAction) = Nothing
        pa = New pointer(Of callbackAction)()
        httpCtx.listenerContext(pa) = ctx
        Dim action As callbackAction = Nothing
        Dim check As _do(Of Boolean) = Nothing
        If +pa Is Nothing Then
            check = callbackAction.pass()
        Else
            check = callbackAction.action_check(+pa)
        End If
        action = New callbackAction(check:=check,
                                    [end]:=Function() As Boolean
                                               Dim accept As Boolean = False
                                               Dim rejectIssues As Byte = 0
                                               If hddosliteEnabled() Then
                                                   accept = (hddoslite.registerAccessing(httpCtx, rejectIssues) =
                                                             ddoslite.returnValue.accept)
                                               Else
                                                   accept = True
                                               End If
                                               If accept Then
                                                   counter.increase(HTTPSERVER3_REQUEST_10sQPS)
                                                   startContext(httpCtx)
                                               Else
                                                   counter.increase(HTTPSERVER3_REJECT_10sQPS)
                                                   rejectContext(httpCtx, rejectIssues)
                                               End If
                                               Return True
                                           End Function)
        assert_begin(action)
    End Sub

    Private Function overConnectionCount() As Boolean
        Return connectionCount() >= maxConnectionCount
    End Function

    Private Sub listen2(Optional ByRef action As callbackAction = Nothing)
        assert(Not pending)
        pending = True
        Dim ctx As pointer(Of HttpListenerContext) = Nothing
        ctx = New pointer(Of HttpListenerContext)()
        action = callbackActionAsyncOperation.ctor(Function(ByRef ac As AsyncCallback,
                                                            ByRef obj As Object) As IAsyncResult
                                                       Return listener.BeginGetContext(ac, obj)
                                                   End Function,
                                                   Function(ByRef ar As IAsyncResult) As HttpListenerContext
                                                       Return listener.EndGetContext(ar)
                                                   End Function,
                                                   Function() As Boolean
                                                       If +ctx Is Nothing Then
                                                           pending = False
                                                       Else
                                                           Dim startticks As Int64 = 0
                                                           startticks = Now.ticks()
                                                           If overConnectionCount() Then
                                                               Dim action2 As callbackAction = Nothing
                                                               action2 = New callbackAction(
                                                                            check:=Function() As Boolean
                                                                                       Return Not overConnectionCount()
                                                                                   End Function,
                                                                            [end]:=Function() As Boolean
                                                                                       startContext(+ctx, startticks)
                                                                                       pending = False
                                                                                       Return True
                                                                                   End Function)
                                                               Return begin(action2)
                                                           Else
                                                               startContext(+ctx, startticks)
                                                               pending = False
                                                           End If
                                                       End If
                                                       Return True
                                                   End Function,
                                                   ctx)
        assert_begin(action)
    End Sub

    Private Sub listen(Optional ByRef action As callbackAction = Nothing)
        action = New callbackAction(check:=Function() As Boolean
                                               If stopping() OrElse Not active() Then
                                                   Return True
                                               Else
                                                   If Not pending Then
                                                       listen2()
                                                   End If
                                                   Return False
                                               End If
                                           End Function)
        begin(action)
    End Sub

    Public Function start() As Boolean
        If Not active() Then
            stopping = False
            importPrefixes()
            Try
                listener.Start()
            Catch ex As Exception
                raiseError(errorHandle.errorType.exclamation, "cannot start HttpListener, ex ", ex.Message)
                Return False
            End Try
            If hddosliteEnabled() Then
                hddoslite.setResourceCount(maxConnectionCount)
            End If
            listen()
            Return True
        Else
            Return False
        End If
    End Function

    Shared Sub New()
        HTTPSERVER3_REQUEST_10sQPS = counter.registerRateAndLastRate("HTTPSERVER3_REQUEST_10sQPS", intervalScale:=10)
        HTTPSERVER3_REJECT_10sQPS = counter.registerRateAndLastRate("HTTPSERVER3_REJECT_10sQPS", intervalScale:=10)
        HTTPSERVER3_REQUEST_RESPOND_TIMEMS = counter.registerAverageAndLastAverage("HTTPSERVER3_REQUEST_RESPOND_TIMEMS")
        HTTPSERVER3_REJECT_RESPOND_TIMEMS = counter.registerAverageAndLastAverage("HTTPSERVER3_REJECT_RESPOND_TIMEMS")
        HTTPSERVER3_STARTCONTEXT_DELAY_TIMEMS =
            counter.registerAverageAndLastAverage("HTTPSERVER3_STARTCONTEXT_DELAY_TIMEMS")
        assert(HttpListener.IsSupported())
    End Sub

    Public Sub New(Optional ByVal para As parameterDefinition = Nothing)
        listener = New HttpListener()
        listener.IgnoreWriteExceptions = True
        prefixes = New [set](Of String)()
        contextpool = New contextpool_t(para)
    End Sub

    Protected Overrides Sub Finalize()
        listener.Close()
        GC.KeepAlive(listener)
        MyBase.Finalize()
    End Sub
End Class
